package donkeykong;

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.geom.AffineTransform;
import java.io.File;
import java.io.IOException;

import javax.imageio.ImageIO;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.Clip;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.UnsupportedAudioFileException;
import javax.swing.JPanel;

/**
 * 
 * @author Antonio Cebollada, Miguel Castillo, José Ignacio Castro
 *
 */
public class Mario extends JPanel{
	
	/** Movimiento de Mario al andar **/
	private static final int MOVIMIENTO_EN_X = 10;

	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;

	/** Imagenes de Mario **/
	private Image imagenMario,imagenParadoIzq,imagenParadoDch,imagenandardch_1,imagenandardch_2,imagenandarizq_1,imagenandarizq_2,
					imagenSaltarDch,imagenSaltarIzq,imagenMuerto_0_izq,imagenMuerto_0_dch,imagenMuerto_1,imagenMuerto_2,imagenMuerto_3,
					imagenMuerto_4,imagenMuerto_5_izq,imagenMuerto_5_dch,imagenSubiendo_1,imagenSubiendo_2,imagenMazo1_izq,imagenMazo1_dch,
					imagenMazo2_izq,imagenMazo2_dch;

	/** Posicion x de Mario **/
	private int posicion_x;
	/** Posicion y de Mario **/
	private int posicion_y;
	/** Posicion de la esquina de la imagen de mario, dependiendo de hacia qué lado mira **/
	private int esquinapredominante; 
	/** Pantalla en la que esta jugando Mario **/
	private int nivel;
	/** Clip que permite iniciar o parar la musica, entre otras opciones **/
	private static Clip clickClip;
	/** Stream de la musica que suena**/	
	private AudioInputStream musicaSonido;
	
	/** Variable booleana que indica si Mario esta andando hacia la derecha o no **/
	private boolean dcha=false;
	/** Variable booleana que indica si Mario esta andando hacia la izquierda o no **/
	private boolean izq=true;
	
	/** Variable booleana que indica si Mario esta saltando o no **/
	private boolean saltando=false;

	/** Variable booleana que indica si Mario esta andando o no **/
	private boolean andando;

	/** Variable booleana que indica si Mario se esta muriendo o no **/
	private boolean muerto;	

	/** Variable booleana que indica si Mario esta subiendo/bajando una escalera o no **/
	private boolean subiendo;

	
	/** Variable necesaria para mostrar las animaciones de los sprites de Mario **/
	private int sprite;
	
	/** Indica la variacion de la posicion y durante un salto **/
	private int incrementoSalto;

	/** Indica la variacion de la posicion x durante un salto **/
	private int incremento_x;

	/** Vidas que tiene Mario **/
	private int vidasMario;
	
	/** Indica si se debe resetear el nivel (porque Mario ha muerto) o no **/
	private boolean resetearNivel=false;

	/** Variable que indica si Mario es invencible o no **/
	private boolean invencible;
	

	
	/** Constructor 
	 * 
	 * @param posx : posicion x de inicio de Mario
	 * @param posy : posicion y de inicio de Mario
	 * @param nv   : pantalla en la que aparece
	 * @param vidas: numero de vidas de Mario
	 */
	public Mario(int posx, int posy, int nv, int vidas) {
		try {
			vidasMario = vidas;
			nivel = nv;
			setPosicion_x(posx);
			setPosicion_y(posy);
			setEsquinaPredominante(posx);
			setAndando(false);
			setSaltando(false);
			setIncrementoSalto(0);
			imagenParadoDch = ImageIO.read(new File("./images/mario/mario_paradodch.png"));
			imagenParadoIzq = ImageIO.read(new File("./images/mario/mario_paradoizq.png"));
			imagenandardch_1 = ImageIO.read(new File("./images/mario/mario_andadch_1.png"));
			imagenandardch_2 = ImageIO.read(new File("./images/mario/mario_andadch_2.png"));
			imagenandarizq_1 = ImageIO.read(new File("./images/mario/mario_andaizq_1.png"));
			imagenandarizq_2 = ImageIO.read(new File("./images/mario/mario_andaizq_2.png"));
			imagenSaltarDch = ImageIO.read(new File("./images/mario/mario_saltandodch.png"));
			imagenSaltarIzq = ImageIO.read(new File("./images/mario/mario_saltandoizq.png"));
			imagenMuerto_0_dch = ImageIO.read(new File("./images/mario/mario_muerto_0_dch.png"));
			imagenMuerto_0_izq = ImageIO.read(new File("./images/mario/mario_muerto_0_izq.png"));
			imagenMuerto_1 = ImageIO.read(new File("./images/mario/mario_muerto_1.png"));
			imagenMuerto_2 = ImageIO.read(new File("./images/mario/mario_muerto_2.png"));
			imagenMuerto_3 = ImageIO.read(new File("./images/mario/mario_muerto_3.png"));
			imagenMuerto_4 = ImageIO.read(new File("./images/mario/mario_muerto_4.png"));
			imagenMuerto_5_izq = ImageIO.read(new File("./images/mario/mario_muerto_5_izq.png"));
			imagenMuerto_5_dch = ImageIO.read(new File("./images/mario/mario_muerto_5_dch.png"));
			imagenSubiendo_1 = ImageIO.read(new File("./images/mario/mario_sube_1.png"));
			imagenSubiendo_2 = ImageIO.read(new File("./images/mario/mario_sube_2.png"));
			imagenMazo1_izq =ImageIO.read(new File("./images/mario/mario_mazo1_izq.png"));

			imagenMazo2_izq =ImageIO.read(new File("./images/mario/mario_mazo2_izq.png"));

			imagenMazo1_dch =ImageIO.read(new File("./images/mario/mario_mazo1_dch.png"));

			imagenMazo2_dch =ImageIO.read(new File("./images/mario/mario_mazo2_dch.png"));
			imagenMario = imagenParadoDch;
			
		} catch (IOException e) {
			e.printStackTrace();
		}	
	}

	/** Getter de la posicion X de Mario **/
	public int getPosicion_x() {
		return posicion_x;
	}


	/**
	 * Setter de la posicion X de Mario
	 * @param posicion_x : nueva posicion X 
	 */
	public void setPosicion_x(int posicion_x) {
		this.posicion_x = posicion_x;
	}


	/**
	 * Setter de la esquina que predomina de la imagen de Mario
	 * @param i : nueva posicion de la imagen de Mario 
	 */
	public void setEsquinaPredominante(int i) {
		esquinapredominante = i;
		
	}

	/** Getter de la esquina de la imagen de Mario **/
	public int getEsquina(){
		return esquinapredominante;
	}


	/** Getter de la posicion Y de Mario **/
	public int getPosicion_y() {
		return posicion_y;
	}


	/**
	 * Setter de la posicion Y de Mario
	 * @param posicion_y : nueva posicion Y 
	 */
	public void setPosicion_y(int posicion_y) {
		this.posicion_y = posicion_y;
	}

	/** Funcion paintComponent que es propia de los JPanels, que sirve para dibujar en el mismo la imagen que se quiera, entre otras opciones **/
	public void paintComponent(Graphics g) {
		Graphics2D g2d = (Graphics2D) g;
		/** Para hacer operaciones complejas con una imagen, crear un AffineTransform **/
		AffineTransform at = new AffineTransform();
		/** Antes de pintar, modificar los valores de las posiciones y de la esquina segun los valores de incrementos de un salto,
		 * si no se está saltando, no se modifican
		 */

		if (isSaltando()){
			setPosicion_y(getPosicion_y()+getIncrementoSalto());
			
			if (getDesplazamientoSalto()<0){
				for (int i=1;i<=Math.abs(getDesplazamientoSalto());i++){
					setPosicion_x(getPosicion_x()-1);
					setEsquinaPredominante(getEsquina()-1);
				}
			}else{
				for (int i=1;i<=getDesplazamientoSalto();i++){
					setPosicion_x(getPosicion_x()+1);
					setEsquinaPredominante(getEsquina()+1);
				}	
			}

		}
		  
		
		if (isInvencible()){
			animacionInvencible(imagenMario,at,g2d);
		}else{
			at.translate(getPosicion_x(), getPosicion_y()-imagenMario.getHeight(null));

		/**Segun el estado en el que se encuentre mario, pintar una imagen u otra **/
		/** Si esta muerto, pintar la imagen actual que se va modificando en la clase Muerto **/
		if (muerto){
			g2d.drawImage(imagenMario, at, null);
			
		/** Si esta saltando, realizar la animacion de saltar **/
		}else if (saltando){
			animacionSaltar(imagenMario,at,g2d);	
			
		/** Si esta andando, realizar la animacion de andar **/
		}else if (andando){
			animacionAndar(imagenMario,at,g2d);
			
		/** Si esta subiendo, realizar la animacion de subir **/
		}else if (subiendo){
				animacionSubir(imagenMario,at,g2d);
		}

		/** Si no cumple ninguna, dibujar la imagen actual **/
		if ((!andando)&(!saltando)&(!subiendo)){
				g2d.drawImage(imagenMario, at, null);
		}
		}
	}

	private void animacionInvencible(Image imagenMario2, AffineTransform at,Graphics2D g2d) {
		Image image;
		int posicionMazoGrande=0;

		sprite=(sprite+1)%4;
		if (sprite<2){
			posicionMazoGrande=31;
		}
		image = (((sprite<2))? ((MirandoDcha())? imagenMazo1_dch: imagenMazo1_izq ) :((MirandoDcha())? imagenMazo2_dch: imagenMazo2_izq)); 
		at.translate(getPosicion_x()+(MirandoDcha()? 0 : -imagenMario.getWidth(null)/2 - 20), getPosicion_y()-imagenMario.getHeight(null)-posicionMazoGrande);
		g2d.drawImage(image,at,null);
		
		
	}

	/** FUNCIONES 
	 * PARA 
	 * SUBIR/BAJAR 
	 * ESCALERAS
	 */
	
	/** Funcion que realiza los cambios de imagenes para hacer parecer una animacion de subir/bajar una escalera **/
	private void animacionSubir(Image imagen, AffineTransform at,Graphics2D g2d) {
		Image image;
		image = (((sprite==0))? imagenSubiendo_1 :imagenSubiendo_2); 	
		g2d.drawImage(image,at,null);
	}
	
	/** Cada vez que se pulsa una tecla de subir/bajar escalera, se llama a esta funcion que alterna las imagenes **/
	public void animacionEscaleras(){
		sprite=(sprite+1)%2;
	}
	
	/** Funcion que dado un color, dice cierto si ese color corresponde con el color del suelo **/
	public boolean EsSuelo(Color color){
		if (nivel == 1){
			return (color.getRed()>250 & color.getGreen()<50 & color.getBlue()>100);
		}else if(nivel == 2){
			return (color.getRed()>230 & color.getGreen()<70 & color.getBlue()>138);
		}
		else{
			return (color.getRed()<30 & color.getGreen()>50 & color.getBlue()>250);
		}
	}
	
	
	/** Funcion que dado un color, dice cierto si ese color corresponde con el color de la base del ascensor **/
	public boolean EsBaseAscensor(Color color){
		return (color.getRed()>253 & color.getGreen()<247 & color.getBlue()==0);
	}
	
	
	/** Funcion que realiza los cambios de imagenes para hacer parecer una animacion de andar por el escenario **/
	private void animacionAndar(Image imagen, AffineTransform at, Graphics2D g2d) {
		Image image;
		/** Si mario esta mirando hacia la derecha, mostrar la animacion para la derecha **/
		if (imagen==imagenParadoDch){
			image = (((sprite==0))? imagenandardch_1:imagenandardch_2); 
			g2d.drawImage(image,at,null);
			sprite=(sprite+1)%2;
			
		/** Si mario esta mirando hacia la izquierda, mostrar la animacion para la izquierda **/
		}else if (imagen==imagenParadoIzq){
			image = (((sprite==0))? imagenandarizq_1:imagenandarizq_2);
			g2d.drawImage(image,at,null);
			sprite=(sprite+1)%2;
		}
	}
	
	/** Funcion que realiza los cambios de imagenes para hacer parecer una animacion de saltar **/
	private void animacionSaltar(Image imagen, AffineTransform at, Graphics2D g2d){
		Image image;
		/** Si esta caminando/parado mirando hacia la derecha, mostrar imagen de saltar a la derecha **/
		if ((imagen==imagenParadoDch)|(imagen==imagenandardch_1)|(imagen==imagenandardch_2)){
			image = imagenSaltarDch;
			g2d.drawImage(image,at,null);
			
		/** Si esta caminando/parado mirando hacia la izquierda, mostrar imagen de saltar a la izquierda **/
		}else if ((imagen==imagenParadoIzq)|(imagen==imagenandarizq_1)|(imagen==imagenandarizq_2)){
			image = imagenSaltarIzq;
			g2d.drawImage(image,at,null);
		}
	}
	
	
	
	/** Poner la imagen actual a la de mirando hacia la derecha **/
	public void MostrarParadoDch() {
		imagenMario = imagenParadoDch;
	}
	
	/** Poner la imagen actual a la de mirando hacia la izquierda **/
	public void MostrarParadoIzq() {
		imagenMario = imagenParadoIzq;
	}

	/** Funcion que realiza el movimiento hacia la derecha, si se puede,
	 *  segun los valores de movimiento existentes. Tambien va modificando la esquina predominante,
	 *  fijandose en los valores de 'izq' y 'dch' para saber si estaba mirando antes hacia un lado u otro
	 */
	public void moverDerecha() {
		/** Si no hay choque con la esquina derecha del mapa **/
		if (!detectarColision_dch(getEsquina())){
			imagenMario = imagenParadoDch;
			dcha=true;
			/** Mover a Mario hacia la derecha **/
			setPosicion_x(getPosicion_x()+ MOVIMIENTO_EN_X);
			/** Si antes estaba mirando hacia la izquierda, debemos cambiar la esquina de la base de la imagen **/
			if (izq){
				setEsquinaPredominante(getEsquina()+imagenMario.getWidth(null)+MOVIMIENTO_EN_X);
				/** Como ya no estamos dirigiendonos hacia la izquierda, lo ponemos a falso **/
				izq=false;
				
			/** Si ya estaba antes mirando hacia la derecha, simplemente actualizar la esquina **/
			}else{
				setEsquinaPredominante(getEsquina()+ MOVIMIENTO_EN_X);	
			}
			
		/** Si detecta un limite de pantalla, desactivar el que Mario esté andando **/
		}else{
			setAndando(false);
		}
		
	}

	/** Funcion que realiza el movimiento hacia la izquierda, si se puede,
	 *  segun los valores de movimiento existentes. Tambien va modificando la esquina predominante,
	 *  fijandose en los valores de 'izq' y 'dch' para saber si estaba mirando antes hacia un lado u otro
	 */
	public void moverIzquierda() {
		/** Si no hay choque con la esquina izquierda del mapa **/
		if (!detectarColision_izq(getEsquina())){
			imagenMario = imagenParadoIzq;
			izq=true;
			/** Mover a Mario hacia la izquierda **/
			setPosicion_x(getPosicion_x()- MOVIMIENTO_EN_X);
			/** Si antes estaba mirando hacia la dercha, debemos cambiar la esquina de la base de la imagen **/
			if (dcha){
				setEsquinaPredominante(getEsquina()-imagenMario.getWidth(null)-MOVIMIENTO_EN_X);
				/** Como ya no estamos dirigiendonos hacia la derecha, lo ponemos a falso **/
				dcha=false;
			/** Si ya estaba antes mirando hacia la izquierda, simplemente actualizar la esquina **/
			}else{
				setEsquinaPredominante(getEsquina()-MOVIMIENTO_EN_X);	
			}
			
		/** Si detecta un limite de pantalla, desactivar el que Mario esté andando **/
		}else{
			setAndando(false);
		}
	}

	/**Detectar colision con el fondo de la pantalla (la parte de la izquierda)**/
	public boolean detectarColision_izq(int posicion_x) {
		if (posicion_x<=25){
			return true;
		}
		return false;
	}
	
	/**Detectar colision con el fondo de la pantalla (la parte de la derecha)**/
	public boolean detectarColision_dch(int posicion_x) {
		if (posicion_x>=getParent().getSize().width-25){
			return true;
		}
		return false;
	}

	/** Funcion que indica que Mario esta andando **/
	public void moviendose() {
		setAndando(true);
	}

	/** Funcion que indica que Mario esta parado **/
	public void pararse() {
		setAndando(false);
	}
	
	/** Setter de andar **/
	public void setAndando(boolean b) {
		andando = b;
	}

	/** Setter de saltar **/
	public void setSaltando(boolean b) {
		saltando = b;
	}
	
	/** Getter de saltar, indica si Mario esta saltando o no **/
	public boolean isSaltando() {
		return saltando;
	}	
	
	/** Getter del incremento de la posicion Y en un salto **/
	private int getIncrementoSalto() {
		return incrementoSalto;
	}

	/** Setter del incremento de la posicion Y en un salto **/
	public void setIncrementoSalto(int incrementoSalto) {
		this.incrementoSalto = incrementoSalto;
	}
	
	/** Getter del incremento de la posicion X en un salto **/
	private int getDesplazamientoSalto() {
		return incremento_x;
	}
	
	/** Setter del incremento de la posicion X en un salto **/
	public void setDesplazamientoSalto(int desp) {
		incremento_x = desp;
	}
	
	/** Indica si Mario se esta moviendo(andando) **/
	public boolean isMoviendose() {
		return andando;
	}

	/** Indica si Mario esta mirando a la derecha o no **/
	public boolean MirandoDcha() {
		return (imagenMario==imagenParadoDch? true:false);
	}

	


	/** Funcion que permite obtener el centro de la base de la imagen de Mario, se usa para las escaleras y para tener 
	 * control sobre el suelo que puede pisar 
	 * @return posicion del centro de la base de la imagen de Mario
	 */
	public int getCentroMario(){
		return getPosicion_x()+getImage().getWidth(null)/2;
	}
	
	/** Funcion que permite reproducir un sonido
	 * 
	 * @param sonido : Nombre (sin extension) del sonido a reproducir
	 * @param duracion : Duracion de la musica por si qse quiere repetir indefinidamente o solo unas veces
	 */
	public void escucharSonido(String sonido, int duracion){
		try {
			clickClip = AudioSystem.getClip();
			musicaSonido = AudioSystem.getAudioInputStream(new File("./sounds/"+sonido+".wav"));
			clickClip.open(musicaSonido);
			clickClip.loop(duracion);
			clickClip.start();
		} catch (LineUnavailableException e) {
			e.printStackTrace();
		} catch (UnsupportedAudioFileException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
		
	}
	
	/** Funcion que se utiliza para parar el sonido, si esta activado **/
	public void pararSonido(){
		if(clickClip != null){
			clickClip.stop();
			clickClip.close();
		}
	}

	/** Funcion para obtener la imagen actual de Mario **/
	public Image getImage() {
		return imagenMario;
	}

	/** Funcion que indica que Mario esta subiendo/bajando una escalera **/
	public void subiendoEscalera() {
			subiendo=true;
	}
	
	/** Funcion que indica que Mario deja de subir/bajar una escalera **/
	public void dejaEscalera() {
			subiendo=false;
	}
	
	/** Funcion que devuelve cierto si Mario esta subiendo/bajando una escalera y falso en caso contrario **/
	public boolean isSubiendoEscalera() {
		return subiendo;
	}	
	
	/** Funcion que indica que Mario esta muriendo **/
	public void Muriendo(){
		Mortal();
		muerto = true;
	}
	
	/** Funcion que dice si Mario esta muriendo o no **/
	public boolean isMuriendo() {
		return muerto;
	}

	
	/** Funcion que se invoca cuando Mario muere
	 * 
	 * @param numero_imagen   : el numero de la imagen a mostrar
	 * @param mirandoDerecha  : booleano que sirve para distinguir las imagenes de morir en izquierda o derecha
	 */
	public void setImageMuerto(int numero_imagen,boolean mirandoDerecha) {
			imagenMario = (((numero_imagen==0)? 
					((mirandoDerecha)? imagenMuerto_0_dch:imagenMuerto_0_izq):
						(numero_imagen==1)? imagenMuerto_1:
							((numero_imagen==2)? imagenMuerto_2:
								((numero_imagen==3)? imagenMuerto_3:
									(numero_imagen==4)? imagenMuerto_4:
										((mirandoDerecha)? imagenMuerto_5_dch:
											imagenMuerto_5_izq))))); 
	}

	/** Funcion que resta una vida a las totales de Mario, si estas se acaban, acaba la partida **/
	public void quitarVida() {
			vidasMario--;
			if (vidasMario==0){
				gameOver();
			}
	}

	/** Funcion de acabar la partida, GAME OVER **/
	private void gameOver() {
		System.exit(0); //TODO cambiar esto
	}

	/** Funcion que indica que se debe reiniciar el nivel, por muerte de Mario **/
	public void iniciarNivel() {
		resetearNivel=true;
	}

	/** Funcion que comprueba si se debe reiniciar el nivel **/
	public boolean isResetearNivel() {
		return resetearNivel;
	}

	/** Funcion para obtener el numero de vidas restantes de Mario **/
	public int getVidas() {
		return vidasMario;
	}

	/** Pone el sprite mirando a la Pauline **/
	public void pasarPantalla() {
		imagenMario = imagenParadoIzq;		
	}

	/** Funcion que se invoca cuando Mario coge un Mazo, volviendose inmune a los enemigos durante un tiempo (10 segundos)**/
	public void Invencible() {
		Invulnerable in = new Invulnerable(this);
		Thread d = new Thread(in);
		d.setName("INVENCIBLE");
		d.start();
		invencible = true;
	}

	/** Funcion que comprueba si Mario es invencible o no **/
	public boolean isInvencible(){
		return invencible;
	}
	
	/** Funcion que se invoca cuando se agota el efecto de inmunidad, dejando a Mario vulnerable a los ataques enemigos **/
	public void Mortal(){
		invencible = false;
	}

	/** Aumenta vidas cuando se supera una puntuacion **/
	public void AumentarVidas() {
		// TODO Auto-generated method stub
		vidasMario++;
	}

}
